﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint;
using System.Web.UI.WebControls.WebParts;
using Microsoft.SharePoint.WebPartPages;
using System.Data;
using System.Web;
using System.IO;
using Microsoft.SharePoint.WebControls;
using Microsoft.SharePoint.Utilities;
using System.Web.UI;
using System.Reflection;
using System.Xml;
using System.Web.Services;
using System.Text.RegularExpressions;
using System.Collections;
using System.Web.UI.WebControls;

namespace PS.WebParts.Rollup.Export
{
    /// <summary>
    /// Summary description for $codebehindclassname$
    /// </summary>
    [WebService(Namespace = "http://portalsolutions.net/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    public class RollupDS : IHttpHandler
    {        
        public void ProcessRequest(HttpContext context)
        {
            context.Response.ContentType = "text/xml";

            RollUpDsParameters param = RollUpDsParameters.GetParameters(context);

            //IHttpHandlerFactory factory = new DummyPageHandlerFactory();

            //factory.GetHandler(null, null, this.RedirectionUrl, null);

            //IHttpHandler h1 = System.Web.UI.PageParser.GetCompiledPageInstance(param.PageUrl, context.Server.MapPath(param.PageUrl), context);

            //Page pp = (Page)h1;

            DataFormWebPart rollUp = null;

            try
            {
                rollUp = GetWebPart(param);

                if (rollUp != null)
                    PrintXml(rollUp, context, param);

            }
            catch (Exception ex)
            {
                string error = string.Format("<ds>{0}{1}</ds>", ex.Message, ex.StackTrace);

                context.Response.Write(error);
            }
            
            context.Response.End();
        }

        private void PrintXml(DataFormWebPart rollUp, HttpContext context, RollUpDsParameters parameters)
        {
            string xml = string.Empty;
            string Rows = "Rows";

            rollUp.DataBind();

            if (rollUp is RollUpDataFormWebPart)
            {
                #region Rollup Case
                xml = ((RollUpDataFormWebPart)rollUp).GetXmlData();

                if (!String.IsNullOrEmpty(parameters.TableName))
                    xml = xml.Replace(Rows + ">", parameters.TableName + ">"); 
                #endregion
            }
            else
            {
                #region Generic Case
                if (rollUp.DataSource is SPDataSource)
                {
                    //TODO: USe String Builder
                    
                    SPDataSource ds = (SPDataSource)rollUp.DataSource;

                    XmlDataSource xmlDS = new XmlDataSource();

                    var args = new System.Web.UI.DataSourceSelectArguments();

                    IEnumerable enums = ds.GetView().Select(args);

                    foreach (SPDataSourceViewResultItem item in enums)
                    {
                        SPListItem li = (SPListItem)item.ResultItem;

                        xml += li.Xml;
                    }                     
                }

                if (!String.IsNullOrEmpty(parameters.TableName))
                    Rows = parameters.TableName;

                xml = string.Format("<DocumentElement>{0}</DocumentElement>", xml.Replace("z:row", Rows).Replace("xmlns:z='#RowsetSchema'", string.Empty));
                #endregion
            }            

            context.Response.Write(xml);
        }

        #region DataSourceString Parse Methods
        private DataFormWebPart GetWebPart(RollUpDsParameters param)
        {
            DataFormWebPart result = null;

            SPWeb currentWeb = SPContext.Current.Web;

            string webUtl = currentWeb.Url;
            
            if (currentWeb.CurrentUser != null)
            {
                string cUser = currentWeb.CurrentUser.Name;
            }

            SPLimitedWebPartManager WebPartMgr = currentWeb.GetLimitedWebPartManager(param.PageUrl, PersonalizationScope.Shared);

            //WebPartManager.
            string webPartTitle = param.WebPartTitle.ToLower().Replace(" ", string.Empty);

            foreach (System.Web.UI.WebControls.WebParts.WebPart webPart in WebPartMgr.WebParts)
            {
                if (webPart is DataFormWebPart)
                {
                    #region RollUp
                    if (webPart.Title.ToLower().Replace(" ", string.Empty) == webPartTitle)
                    {
                        result = GetRollupWebPart(webPart);

                        break;
                    }
                    #endregion
                }                
            }

            return result;
        }

        private void ParseProperty(string aName, string pValue, object oo, Type type, Dictionary<string, DataAssemblyInfo> prefixDic)
        {
            #region Process Attributes            

            PropertyInfo info = type.GetProperty(aName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);

            if (info == null) return;

            if (!info.CanWrite) return;

            object value = null;

            Type propertyType = info.PropertyType;

            if (propertyType == typeof(String))
                value = pValue;            
            else if (propertyType.IsEnum)
                value = Enum.Parse(propertyType, pValue);
            else if (propertyType == typeof(Boolean))
                value = bool.Parse(pValue);
            else if (propertyType == typeof(int))
                value = int.Parse(pValue);
            else if (propertyType == typeof(Guid))
                value = new Guid(pValue);            
            else
                throw new Exception("Property type not handled: " + propertyType.Name);

            info.SetValue(oo, value, null);
            #endregion
        }

        private object ParseNode(XmlNode node, Dictionary<string, DataAssemblyInfo> prefixDic)
        {
            #region Process node

            string prefix = node.Name.Split(':')[0];

            DataAssemblyInfo seri = prefixDic[prefix.ToLower()];

            Type uls = seri.AssemblyReference.GetType(seri.NameSpace + "." + node.LocalName, false, true);

            object rawDS = Activator.CreateInstance(uls);

            foreach (XmlAttribute attribute in node.Attributes)
            {
                ParseProperty(attribute.Name, attribute.Value, rawDS, uls, prefixDic);
            }

            foreach (XmlNode childNode in node.ChildNodes)
            {
                #region Process Children
                string elementName = childNode.Name;

                PropertyInfo info = uls.GetProperty(elementName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
                
                if (info == null) continue;

                Type t = info.PropertyType;
                
                if (!info.CanWrite)
                {
                    // Check if this is a collection
                    object ds = info.GetValue(rawDS, null);

                    try
                    {
                        MethodInfo addMethod = t.GetMethod("Add");

                        if (addMethod != null)                        
                        foreach (XmlNode child in childNode.ChildNodes)
                        {
                            object oo = ParseNode(child, prefixDic);
                            
                            addMethod.Invoke(ds, new object[] { oo });                        
                        }   
                    }
                    catch (Exception ex)
                    {
                        foreach (XmlNode child in childNode.ChildNodes)
                        {
                            object oo = ParseNode(child, prefixDic);
                            
                            t.InvokeMember("Add", BindingFlags.InvokeMethod,Type.DefaultBinder,ds, new object[] { oo });                            
                        }

                        //call parameter-ed overload 
                        
                    }                                                         
                }
                else if (!elementName.Contains(':'))
                {
                    ParseProperty(elementName, childNode.InnerXml, rawDS, uls, prefixDic);
                }
                else
                {
                    //control
                }
                #endregion
            }
            
            #endregion

            return rawDS;
        }

        private DataFormWebPart GetRollupWebPart(System.Web.UI.WebControls.WebParts.WebPart webPart)
        {
            DataFormWebPart dfwp = (DataFormWebPart)webPart;

            XmlDocument doc = GetXmlDocument(dfwp.DataSourcesString);

            Dictionary<string, DataAssemblyInfo> prefixDic = GetAssemblyDictionary(doc);

            foreach (XmlNode node in doc.DocumentElement.ChildNodes)
            {
                object ds = ParseNode(node, prefixDic);                

                dfwp.DataSources.Add((IDataSource)ds);                
            }

            return dfwp;
        }

        private Dictionary<string, DataAssemblyInfo> GetAssemblyDictionary(XmlDocument doc)
        {
            Dictionary<string, DataAssemblyInfo> prefixDic = new Dictionary<string, DataAssemblyInfo>();

            XmlNodeList registers = doc.SelectNodes("//Register");

            foreach (XmlNode node in registers)
            {
                #region Get Assemblies/Namespace
                string tagprefix = string.Empty;
                string assembly = string.Empty;
                string nameSpace = string.Empty;

                foreach (XmlAttribute attribute in node.Attributes)
                {
                    if (String.Equals(attribute.Name, "TagPrefix", StringComparison.CurrentCultureIgnoreCase))
                        tagprefix = attribute.Value;
                    else if (String.Equals(attribute.Name, "Assembly", StringComparison.CurrentCultureIgnoreCase))
                        assembly = attribute.Value;
                    else if (String.Equals(attribute.Name, "Namespace", StringComparison.CurrentCultureIgnoreCase))
                        nameSpace = attribute.Value;
                }

                if (String.IsNullOrEmpty(tagprefix))
                    continue;

                if (!prefixDic.ContainsKey(tagprefix))
                {
                    DataAssemblyInfo seri = new DataAssemblyInfo();

                    seri.AssemblyName = assembly;

                    Assembly assm = Assembly.Load(assembly);

                    seri.AssemblyReference = assm;

                    seri.NameSpace = nameSpace;

                    prefixDic.Add(tagprefix, seri);
                }
                #endregion

                doc.DocumentElement.RemoveChild(node);
            }

            DataAssemblyInfo sri = new DataAssemblyInfo();
                        
            sri.AssemblyReference = Assembly.GetAssembly(typeof(Parameter));

            sri.AssemblyName =  sri.AssemblyReference.FullName;

            sri.NameSpace = typeof(Parameter).Namespace;

            prefixDic.Add("asp", sri);

            return prefixDic;
        }

        private string[] GetPrefixes(string rawString)
        {
            List<String> rString = new List<String>();

            string pTagPrefix = "TagPrefix=\\\"((?:\\w*\\d*)+)\\\"";
            //"TagPrefix=\\[\"|']((?:\\w*\\d*)+)\\[\"|']"

            Regex pRgx = new Regex(pTagPrefix, RegexOptions.IgnoreCase);

            MatchCollection pMatches = pRgx.Matches(rawString);

            foreach (Match pMatch in pMatches)
            {
                string pGroup = pMatch.Groups[1].Value;

                rString.Add(pGroup);
            }

            if (!rString.Contains("asp"))
                rString.Add("asp");

            return rString.ToArray();
        }

        private XmlDocument GetXmlDocument(string dataSourceString)
        {
            //"<%@ Register TagPrefix=\"webparts\" Namespace=\"PS.WebParts\" Assembly=\"PS.WebParts.Rollup, Version=14.0.0.0, Culture=neutral, PublicKeyToken=90e3045b123af1c3\" %>";

            //string dataSourceString = "<ds xmlns:sharepoint='http://www.mysite.com/'  xmlns:asp='http://www.mysite.com1/'  xmlns:cc1='http://www.mysite.com2/'>" +
            //                            dfwp.DataSourcesString.Replace("<%@", "<").Replace("%>", "/>").Replace("< Register", "<Register") +
            //                            "</ds>";

            //XmlDocument doc = GetXmlDocument(dataSourceString);

            //XmlNamespaceManager xnsm = new XmlNamespaceManager(doc.NameTable);

            //xnsm.AddNamespace("sharepoint", "http://www.mysite.com/");
            //xnsm.AddNamespace("asp", "http://www.mysite.com1/");
            //xnsm.AddNamespace("cc1", "http://www.mysite.com2/");

            //doc.LoadXml(dataSourceString);

            string registerString = dataSourceString.Substring(0, dataSourceString.LastIndexOf("%>")).ToLower();

            string[] rawPrefixes = GetPrefixes(registerString);

            List<string> prefixes = new List<string>();

            XmlDocument doc = new XmlDocument();

            XmlNamespaceManager xnsm = new XmlNamespaceManager(doc.NameTable);            

            string namespaceString = string.Empty;
            string namespaceTemplate = " xmlns:{0}='{0}'";

            foreach (string bit in rawPrefixes)
            {
                if (!prefixes.Contains(bit))
                {
                    prefixes.Add(bit);

                    xnsm.AddNamespace(bit, bit);

                    namespaceString += String.Format(namespaceTemplate, bit);
                }
            }

            string template = "<ds{0}>{1}</ds>";

            dataSourceString = string.Format(template, namespaceString, dataSourceString.Replace("<%@", "<").Replace("%>", "/>").Replace("< Register", "<Register"));

            doc.LoadXml(dataSourceString);

            return doc;
        } 
        #endregion
        
        public bool IsReusable
        {
            get
            {
                return false;
            }
        }

        #region Helper Classes
        private class DummyPageHandlerFactory : PageHandlerFactory
        {
        }

        /// <summary>
        /// Stores assembly references in DataSourceString
        /// Used to create datasources from strings
        /// </summary>
        public class DataAssemblyInfo
        {
            public DataAssemblyInfo()
            {

            }

            public string NameSpace { get; set; }
            public string AssemblyName { get; set; }
            //TODO: Multiple assemblies per prefix is possible -> AssemblyReference should be a dictionary
            //public Dictionary<string,Assembly> Assemblies = new Dictionary<string,Assembly>();
            public Assembly AssemblyReference { get; set; }
        }


        /// <summary>
        /// Parameter wrapper
        /// </summary>
        public class RollUpDsParameters
        {
            /// <summary>
            /// Url of the page that contains the rollup webpart to be used
            /// </summary>
            public string PageUrl = string.Empty;

            /// <summary>
            /// Title of the webpart
            /// </summary>
            public string WebPartTitle = string.Empty;

            /// <summary>
            /// Elevate the datasource request
            /// </summary>
            /// <remarks>Not bein used right now</remarks>
            public bool Elevate = false;

            /// <summary>
            /// Name of the xml element that will contain the row information. By default this is 'Rows'. 
            /// This value is used by Microsoft Access to set the table name during imports.
            /// </summary>
            /// <example>TableName</example>            
            public string TableName = string.Empty;

            public static RollUpDsParameters GetParameters(HttpContext context)
            {
                RollUpDsParameters parameters = new RollUpDsParameters();

                if (context.Request.Path.EndsWith(".xml", StringComparison.CurrentCultureIgnoreCase))
                {
                    //embedded parameters case
                    #region xml file Case
                    string[] bits = context.Request.Path.Split('/');

                    bits = bits[bits.Length - 1].Split('-');

                    string replaceHelper = "__";

                    foreach (string bit in bits)
                    {
                        string rawParameter = bit;

                        if (rawParameter.EndsWith(".xml", StringComparison.CurrentCultureIgnoreCase))
                        {// Trim extension - This is actually the last case (could be improved)
                            rawParameter = bit.Substring(0, rawParameter.LastIndexOf('.')); 
                        }

                        if (rawParameter.StartsWith("url"))
                        {
                            rawParameter = (replaceHelper + rawParameter).Replace(replaceHelper + "url", string.Empty).Replace('_', '/');
                            parameters.PageUrl = rawParameter;
                        }
                        else if (rawParameter.StartsWith("table", StringComparison.CurrentCultureIgnoreCase))
                        {
                            rawParameter = (replaceHelper + rawParameter).Replace(replaceHelper + "table", string.Empty);
                            parameters.TableName = rawParameter;
                        }
                        else if (rawParameter.StartsWith("webpart", StringComparison.CurrentCultureIgnoreCase))
                        {
                            rawParameter = (replaceHelper + rawParameter).Replace(replaceHelper + "webpart", string.Empty);
                            parameters.WebPartTitle = rawParameter;
                        }                        
                    }
                    
                    #endregion
                }
                else
                {                    
                    #region Query String Case
                    parameters.PageUrl = context.Request.Params["pageurl"];

                    parameters.WebPartTitle = context.Request.Params["webparttitle"];

                    if (context.Request.Params["tablename"] != null)
                    {
                        string tableName = context.Request.Params["tablename"].ToString().Trim();

                        if (!String.IsNullOrEmpty(tableName))
                            parameters.TableName = tableName;
                    } 
                    #endregion
                }
                return parameters;
            }
        } 
        #endregion
    }
}
